# coding: utf-8
'''
Created on May 25, 2010

@author: guard
'''
import sys
import re
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from ui import ui_main
from cgk.game import Game
from plugins.plugin_base import PluginsRegister, PLUGIN_CATEGORIES, PLUGIN_TYPES, uid
from dialogs import *

__version__ = u'0.3'

def clearLayout(layout):
    if layout is None:
        return
    while layout.count():
        item = layout.takeAt(0)
        widget = item.widget()
        if widget is not None:
            widget.deleteLater()
        else:
            clearLayout(item.layout())

class MainWindow(QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        
        self.connect(self.tableResults.verticalScrollBar(), SIGNAL('valueChanged(int)'), 
                self.tablePoints.verticalScrollBar(), SLOT('setValue(int)'))
        self.connect(self.tablePoints.verticalScrollBar(), SIGNAL('valueChanged(int)'), 
                self.tableResults.verticalScrollBar(), SLOT('setValue(int)'))
                
        PluginsRegister.autodiscover()
        self._active_plugins = {}
        for k in PluginsRegister._register:
            self._active_plugins[k] = []

        self._restore_state()

        # default plugins if nothing was found
        if not self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.MAIN)]:
            p = PluginsRegister.plugin_by_name('GamePointsPlugin')
            if p is not None:
                self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.MAIN)].append(p)
        if not self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.ADDITIONAL)]:
            p = PluginsRegister.plugin_by_name('GameRatingPlugin')
            if p is not None:
                self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.ADDITIONAL)].append(p)
        
        gameTotalPlugins = QMenu(u'Хитрый подсчет', self)            
        for p in PluginsRegister.plugins(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.TOTAL):
            a = QAction(u'[{0}] {1}'.format(p.marker, p.full_name), self)
            a.setData(p.__name__)
            self.connect(a, SIGNAL("triggered()"), self.on_trickyResults_triggered)
            gameTotalPlugins.addAction(a)
        # FIXME: rename 1st level menus
        self.menu_4.addMenu(gameTotalPlugins)
                    
        self.open_dialog = QFileDialog(self)
        self.open_dialog.setFileMode(QFileDialog.ExistingFile)
        self.save_dialog = QFileDialog(self)
        self.save_dialog.setFileMode(QFileDialog.AnyFile)
        for dialog in (self.open_dialog, self.save_dialog): 
            dialog.setViewMode(QFileDialog.Detail)
            dialog.setNameFilter("W3W files (*.w3w)")
            
            
        self.game = None
        self.current_question = None
        self.current_team = None
        self.set_game_modified(False)
        
        self.results_dialog = None
        self.installEventFilter(self)
        if self.filename:
            try:
                self.load_game(False)
            except:
                self.filename = None

        self.set_window_title()

    def set_window_title(self):
        s = [u'[*]']
        if self.game:
            s.append(self.game.title)
            if self.filename:
                s.extend((u' [', self.filename, u'] - '))
        s.append(u'CgkCalc by Eugene Mirotin')
        s.append(u' - v. ')
        s.append(__version__)
        self.setWindowTitle(u''.join(s))
        
    def set_game_modified(self, value):
        self.game_modified = value
        self.setWindowModified(value)

    def okToUnloadGame(self):
        if not self.game_modified:
            return True
        r = QMessageBox.warning(self, u'Сохранить игру?', u'Игра не сохранена. Сохранить?', 
                                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if r == QMessageBox.Cancel:
            return False
        elif r == QMessageBox.Yes:
            self.save_game()
        return True
    
    def closeEvent(self, event):
        if self.okToUnloadGame():
            self._save_state()
        else:
            event.ignore()
    
    def save_game(self, save_as=False):
        if not self.game:
            return
        if not self.filename or save_as:
            if self.save_dialog.exec_():
                self.filename = unicode(self.save_dialog.selectedFiles()[0])
            else:
                return
        self.game.to_file(self.filename)
        self.set_game_modified(False)
        
    def load_game(self, request_filename=True):
        if not self.okToUnloadGame():
            return
        if request_filename:
            if self.open_dialog.exec_():
                self.filename = unicode(self.open_dialog.selectedFiles()[0])
            else:
                return
        if self.results_dialog:
            self.results_dialog.close()
            self.results_dialog = None
        self.game = Game.from_file(self.filename)
        self.set_game_modified(False)
        self.set_window_title()
        self.setupGameUI()
        
    def create_game(self):
        if not self.okToUnloadGame():
            return
        d = GamePropertiesDialog(self)
        d.deGameDate.setDate(QDate.currentDate())
        if not d.exec_():
            return
        if self.results_dialog:
            self.results_dialog.close()
            self.results_dialog = None
        self.game = Game(d.spinQuestionsNumber.value(), 
                         unicode(d.leGameTitle.text()), 
                         d.deGameDate.date().toString('dd.MM.yyyy'))
        self.filename = None
        d = TeamsPropertiesDialog(self.game, self.recent_cities, self)
        if d.exec_():
            self.recent_cities = d.cities
        self.set_game_modified(True)
        self.set_window_title()
        self.setupGameUI()

    
    def _save_state(self):
        settings = QSettings()
        settings.setValue("LastFile", self.filename)
        settings.setValue("MainWindow/Geometry", self.saveGeometry())
        settings.setValue("MainWindow/State", self.saveState())
        settings.setValue("MainWindow/splitter_hor", self.splitter_hor.saveState())
        settings.setValue("MainWindow/splitter_vert", self.splitter_vert.saveState())
        
        settings.setValue("Plugins/Game/Main", 
                          ','.join(p.__name__ for p in self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.MAIN)]))
        settings.setValue("Plugins/Game/Additional", 
                          ','.join(p.__name__ for p in self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.ADDITIONAL)]))

        settings.setValue("UsedCities", u'|'.join(self.recent_cities))

            
    def _restore_state(self):
        settings = QSettings()
        self.restoreState(settings.value('MainWindow/State').toByteArray())        
        self.restoreGeometry(settings.value('MainWindow/Geometry').toByteArray())
        self.splitter_hor.restoreState(settings.value('MainWindow/splitter_hor').toByteArray())
        self.splitter_vert.restoreState(settings.value('MainWindow/splitter_vert').toByteArray())
        
        self.filename = settings.value('LastFile')
        if self.filename.isNull():
            self.filename = None
        else:
            self.filename = unicode(self.filename.toString())
        
        for pname in settings.value("Plugins/Game/Main").toString().split(','):
            for p in PluginsRegister.plugins(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.MAIN):
                if p.__name__ == pname:
                    self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.MAIN)].append(p)
        for pname in settings.value("Plugins/Game/Additional").toString().split(','):
            for p in PluginsRegister.plugins(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.ADDITIONAL):
                if p.__name__ == pname:
                    self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.ADDITIONAL)].append(p)
                    
        self.recent_cities = [unicode(s) for s in settings.value("UsedCities").toString().split(u'|')]
        self.recent_cities = [s for s in self.recent_cities if s]

    def eventFilter(self, obj, event):
        if event.type() == QEvent.KeyPress and event.key() == Qt.Key_F5 and self.results_dialog:
            self.results_dialog.fill_table()
            return True
        return QWidget.eventFilter(self, obj, event)

    @pyqtSignature('')
    def on_menuGamePlugins_triggered(self):
        d = GamePluginsDialog(PluginsRegister, self._active_plugins, self)
        if d.exec_():
            self.updatePoints()

    @pyqtSignature('')
    def on_menuLoadGame_triggered(self):
        self.load_game()
        
    @pyqtSignature('')
    def on_menuSaveGame_triggered(self):
        self.save_game()

    @pyqtSignature('')
    def on_menuSaveGameAs_triggered(self):
        self.save_game(True)
        self.set_window_title()

    @pyqtSignature('')
    def on_menuNewGame_triggered(self):
        self.create_game()

    @pyqtSignature('')
    def on_menuGameProperties_triggered(self):
        if not self.game:
            return
        d = GamePropertiesDialog(self)
        d.leGameTitle.setText(self.game.title)
        d.deGameDate.setDate(QDate.currentDate())
        d.spinQuestionsNumber.setValue(self.game.questions_number)
        if d.exec_():
            self.game.title = unicode(d.leGameTitle.text())
            self.game.date = unicode(d.deGameDate.date().toString('dd.MM.yyyy'))
            n = int(d.spinQuestionsNumber.value())
            if n != self.game.questions_number:
                self.game.set_questions_number(n)
            self.setupGameUI()
            self.set_game_modified(True)
            self.set_window_title()
            
    @pyqtSignature('')
    def on_menuEditTeamsList_triggered(self):
        if not self.game:
            return
        d = TeamsPropertiesDialog(self.game, self.recent_cities, self)
        if d.exec_():
            self.recent_cities = d.cities
            self.set_game_modified(True)
            self.setupGameUI()
            
    @pyqtSignature('')
    def on_menuEditGroupsList_triggered(self):
        if not self.game:
            return
        d = GroupsPropertiesDialog(self.game, self)
        if d.exec_():
            self.set_game_modified(True)
            # FIXME: may be needed to uncomment later
#            self.setupGameUI()

    @pyqtSignature('')
    def on_menuQuit_triggered(self):
        self.close()
        
    @pyqtSignature('')
    def on_menuShowScreenResults_triggered(self):
        self.results_dialog = ScreenResultsDialog(self.game, self.calc_points, self.calc_places, self)
        self.results_dialog.show()
        
    def on_trickyResults_triggered(self):
        a = self.sender()
        #print a.data().toString()

    @pyqtSignature("QTableWidgetItem *")
    def on_tableResults_itemClicked(self, ti):
        data = self.tableResults.horizontalHeaderItem(ti.column()).data(Qt.UserRole).toList()
        if len(data) >= 2:
            return
        team_id = ti.data(Qt.UserRole).toInt()[0]
        value = ti.checkState() == Qt.Checked
        question_number = self._orig_index(ti.column())
        self.setResult(team_id, question_number, value, False)
        self.set_current_question(question_number)
        self.set_current_team(team_id)
        
    @pyqtSignature('')
    def on_tbCollapseColumns_triggered(self):
        if not self.game:
            return
        tr = self.tableResults
        left = self.game.questions_number
        right = -1
        for r in tr.selectedRanges():
            left = min(left, r.leftColumn())
            right = max(right, r.rightColumn())
        self._collapse_columns(left, right)

    @pyqtSignature('')
    def on_tbExpandColumns_triggered(self):
        if not self.game:
            return
        tr = self.tableResults
        if len(tr.selectedRanges()) > 1:
            return
        r = tr.selectedRanges()[0]
        if r.rightColumn() > r.leftColumn():
            return
        self._expand_columns(r.leftColumn())

    @pyqtSignature('')
    def on_bDecCurrentQuestion_clicked(self):
        if self.current_question == 1:
            return
        self.set_current_question(self.current_question - 1)
        
    @pyqtSignature('')
    def on_bIncCurrentQuestion_clicked(self):
        if not self.game or self.current_question == self.game.questions_number:
            return
        self.set_current_question(self.current_question + 1)
        
    @pyqtSignature('int')
    def on_spinCurrentQuestion_valueChanged(self, val):
        self.set_current_question(val - 1, False)

    @pyqtSignature('')
    def on_bAllPlus_clicked(self):
        for team_id in self.game.teams:
            self.setResult(team_id, self.current_question, True)

    @pyqtSignature('')
    def on_bAllMinus_clicked(self):
        for team_id in self.game.teams:
            self.setResult(team_id, self.current_question, False)
            
    @pyqtSignature('')
    def on_bInvert_clicked(self):
        for team_id in self.game.teams:
            self.setResult(team_id, self.current_question, not self.game.result(team_id, self.current_question))

    @pyqtSignature('int')
    def on_cbTeam_currentIndexChanged(self, i):
        if not hasattr(self, '_question_buttons'):
            return
        d = self.cbTeam.itemData(i).toInt()[0]
        self.set_current_team(d, False)
            
    @pyqtSignature('')
    def on_leManualTeamsInput_returnPressed(self):
        l = re.split(',?\s+', self.leManualTeamsInput.text())
        r = []
        for i in l:
            try:
                r.append(int(i))
            except ValueError:
                pass
        for i in r:
            for team_id in self.game.teams:
                if self.game.teams[team_id].num == i:
                    self.setResult(team_id, self.current_question, True)

    @pyqtSignature('')
    def on_leManualTeamsInput_editingFinished(self):
        self.leManualTeamsInput.setText('')
        
    @pyqtSignature('')
    def on_leManualQuestionsInput_returnPressed(self):
        l = re.split('(,?\s+)', self.leManualQuestionsInput.text())
        r = []
        for i in l:
            try:
                r.append(int(i))
            except ValueError:
                pass
        for i in r:
            self.setResult(self.current_team, i - 1, True)

    @pyqtSignature('')
    def on_leManualQuestionsInput_editingFinished(self):
        self.leManualQuestionsInput.setText('')
    

    def _orig_index(self, i):
        ti = self.tableResults.horizontalHeaderItem(i)
        return ti.data(Qt.UserRole).toInt()[0]

    def _collapse_columns(self, left, right):
        if left >= right:
            return
        i = left
        while i <= right:
            q = self._expand_columns(i)
            if q[0]:
                right -= 1
                left = min(left, q[1][0])
                i = q[1][1]
            i += 1
        tr = self.tableResults
        tr.clearSelection()
        for i in range(left, right+1):
            tr.hideColumn(i)
        i, j = [self._orig_index(a) for a in (left, right)]
        tr.insertColumn(right + 1)
        tr.setHorizontalHeaderItem(right + 1, QTableWidgetItem('{0}-{1}'.format(i+1, j+1)))
        tr.horizontalHeaderItem(right + 1).setData(Qt.UserRole, [left, right])
        tr.setColumnWidth(right + 1, 40)

        for j in range(right + 2, tr.columnCount()):
            data = tr.horizontalHeaderItem(j).data(Qt.UserRole).toList()
            if len(data) < 2:
                continue
            l_ = [x.toInt()[0] + 1 for x in data]
            tr.horizontalHeaderItem(j).setData(Qt.UserRole, l_)
        
        for i in range(tr.rowCount()):
            team_id = tr.verticalHeaderItem(i).data(Qt.UserRole).toInt()[0]
            ti = QTableWidgetItem(unicode(self.game.total(team_id, left, right)))
            ti.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            tr.setItem(i, right+1, ti)
    
    def _expand_columns(self, column):
        tr = self.tableResults
        data = tr.horizontalHeaderItem(column).data(Qt.UserRole).toList()
        if len(data) <= 1:
            return 0, None
        l = [x.toInt()[0] for x in data]
        tr.removeColumn(l[1]+1)
        for i in range(l[0], l[1] + 1):
            tr.showColumn(i)
        for j in range(l[1]+1, tr.columnCount()):
            data = tr.horizontalHeaderItem(j).data(Qt.UserRole).toList()
            if len(data) < 2:
                continue
            l_ = [x.toInt()[0]-1 for x in data]
            tr.horizontalHeaderItem(j).setData(Qt.UserRole, l_)
        return 1, l
            
    def setResult(self, team_id, question_number, value, updateTable=True):
        tt = self.tableResults
        for i in range(tt.rowCount()):
            d = self.tableResults.verticalHeaderItem(i).data(Qt.UserRole).toInt()[0]
            if d != team_id:
                continue
            else:
                self.set_game_modified(True)
                if updateTable:
                    self.tableResults.item(i, question_number).setCheckState(Qt.Checked if value else Qt.Unchecked)
                self.cbTeam.setCurrentIndex(i)
                self.game.setResult(team_id, question_number, value)
                self.updateCollapsedCounts(question_number)
                self.updatePoints()
                self.color_team_buttons(self.current_question)
                self.color_question_buttons(self.current_team)
                break
            
    def updateCollapsedCounts(self, question_number):
        tr = self.tableResults
        for i in range(tr.columnCount()):
            data = tr.horizontalHeaderItem(i).data(Qt.UserRole).toList()
            if len(data) < 2:
                continue
            l_ = [x.toInt()[0] for x in data]
            if l_[0] <= question_number <= l_[1]:
                for i in range(tr.rowCount()):
                    team_id = tr.verticalHeaderItem(i).data(Qt.UserRole).toInt()[0]
                    tr.setItem(i, l_[1] + 1, 
                               QTableWidgetItem(unicode(self.game.total(team_id, *l_))))
                break        
            
    def setupGameUI(self):
        self.spinCurrentQuestion.setMaximum(self.game.questions_number)
        
        tr = self.tableResults
        tr.clear() 
        tr.setColumnCount(self.game.questions_number)
        tr.setRowCount(len(self.game.teams))
        tr.setHorizontalHeaderLabels([unicode(x) for x in range(1, self.game.questions_number + 1)])
        for i in range(self.game.questions_number):
            tr.horizontalHeaderItem(i).setData(Qt.UserRole, i)
            tr.setColumnWidth(i, 24)
        
        x = sorted([(t.num, t.name, t.id) for t in self.game.teams.values()])
        tr.setVerticalHeaderLabels([u'{0}. {1}'.format(t[0], t[1]) for t in x])
        for i in range(len(x)):
            res = self.game.results[x[i][2]]
            for j in range(self.game.questions_number):            
                ti = QTableWidgetItem()
                ti.setCheckState(Qt.Checked if res[j] else Qt.Unchecked)
                tr.setItem(i, j, ti)
                ti.setData(Qt.UserRole, x[i][2]) # team id
            tr.verticalHeaderItem(i).setData(Qt.UserRole, x[i][2])
        self.cbTeam.clear()
        for t in x:
            self.cbTeam.addItem(u'{0}. {1}'.format(t[0], t[1]), t[2])

        self.updatePoints()
        
        self._team_buttons = {}
        self._question_buttons = {}    
        
        l = self.scrollTeamsNumbersContents.layout()
        if l:
            g = l
            clearLayout(l)
        else:
            g = QGridLayout()
        if self.game.teams:
            tt = [(team.num - 1, team.id) for team in self.game.teams.values()]
            teams = {}
            for t in tt:
                teams[t[0]] = t[1]
            for j in range(max(teams.keys()) + 1):
                i = j // 10
                b = QPushButton(unicode(j + 1))
                if j in teams:
                    self.connect(b, SIGNAL('clicked()'), self.team_button_clicked)
                    self._team_buttons[b] = [teams[j], False]
                else:
                    b.setEnabled(False)
                    b.setStyleSheet('border: 0; color: gray')
                g.addWidget(b, i, j - (i * 10))
            if not l:
                self.scrollTeamsNumbersContents.setLayout(g) 
            self.set_current_team(self.current_team or min(self.game.teams.keys())) 
                
        l = self.scrollQuestionsNumbersContents.layout()
        if l:
            g = l
            clearLayout(l)
        else:
            g = QGridLayout()
        for j in range(self.game.questions_number):
            i = j // 10
            b = QPushButton(unicode(j + 1))
            self.connect(b, SIGNAL('clicked()'), self.question_button_clicked)
            self._question_buttons[b] = [j, False]                    
            g.addWidget(b, i, j - (i * 10))
        if not l:
            self.scrollQuestionsNumbersContents.setLayout(g)
        
        self.set_current_question(self.current_question or 0)
    
    def set_current_question(self, question_number, updateSpin=True):
        self.current_question = question_number
        if updateSpin:
            self.spinCurrentQuestion.setValue(question_number + 1)
        self.color_team_buttons(question_number)
        if self.game and self.game.teams:
            self.color_question_buttons(self.current_team or min(self.game.teams.keys()))
            
    def set_current_team(self, team_id, updateCombo=True):
        self.current_team = team_id
        self.color_question_buttons(team_id)
        if self.game and self.game.questions_number:
            self.color_team_buttons(self.current_question or 0)
        if updateCombo:
            for i in range(len(self.game.teams)):
                d = self.cbTeam.itemData(i).toInt()[0]
                if d != team_id:
                    continue
                else:
                    self.cbTeam.setCurrentIndex(i)
                    break
                
    def color_team_buttons(self, question_number):
        for b in self._team_buttons:
            self._team_buttons[b][1] = self.game.result(self._team_buttons[b][0], question_number) 
            b.setStyleSheet('color: {0};'.format('green' if self._team_buttons[b][1] else 'red'))

    def color_question_buttons(self, team_id):
        for b in self._question_buttons:
            self._question_buttons[b][1] = self.game.result(team_id, self._question_buttons[b][0]) 
            b.setStyleSheet('color: {0};'.format('green' if self._question_buttons[b][1] else 'red'))

      
    def team_button_clicked(self):
        b = self.sender()
        if b not in self._team_buttons:
            return
        r = self._team_buttons[b]
        r[1] = not r[1]
        self.setResult(r[0], self.current_question, r[1])

    def question_button_clicked(self):
        b = self.sender()
        if b not in self._question_buttons:
            return
        r = self._question_buttons[b]
        r[1] = not r[1]
        self.setResult(self.current_team, r[0], r[1])
            
    def calc_points(self):
        res = {}
        for tid in self.game.teams:
            res[tid] = []
        pl = self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.MAIN)] + \
            self._active_plugins[uid(PLUGIN_CATEGORIES.GAME, PLUGIN_TYPES.ADDITIONAL)]
        # FIXME: ordering of add plugins
        for p in pl:
            r = p.calculate(self.game)
            for tid in self.game.teams:
                res[tid].append(r.get(tid, None))
        return res, pl
    
    def calc_places(self, rr):
        r = []
        for tid in rr:
            r.append(rr[tid] + [tid])
        d = {}
        p = 0
        i = 1
        prev = None
        for a in reversed(sorted(r)):
            tid = a[-1]
            res = a[:-1]
            if res != prev:
                p += i
                i = 1
            else:
                i += 1 
            d[tid] = p
            prev = res
        return d 
            
    def updatePoints(self):
        if not self.game.teams:
            return
        rr, pl = self.calc_points()
        tp = self.tablePoints
        tp.clear()
        tp.setColumnCount(len(pl))
        tp.setRowCount(len(self.game.teams))
        i = 0
        places = self.calc_places(rr)
        for p in pl:
            tp.setHorizontalHeaderItem(i, QTableWidgetItem(p.marker))
            for j in range(len(self.game.teams)):
                team_id = self.tableResults.verticalHeaderItem(j).data(Qt.UserRole).toInt()[0]
                if team_id in rr and rr[team_id][i] is not None:
                    tp.setItem(j, i, QTableWidgetItem(unicode(rr[team_id][i])))
            i += 1
        for j in range(len(self.game.teams)):
            team_id = self.tableResults.verticalHeaderItem(j).data(Qt.UserRole).toInt()[0]
            tp.setVerticalHeaderItem(j, QTableWidgetItem(unicode(places.get(team_id, ''))))
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setApplicationName('CgkCalc')
    app.setOrganizationName('Eugene Mirotin')
    app.setOrganizationDomain('emirotin@gmail.com')
    
    form = MainWindow()
    form.show()
    
    sys.exit(app.exec_())
